/*  =====================================================================
    zhelpers.h

    Helper header file for example applications.
    =====================================================================
*/

#ifndef __ZHELPERS_H_INCLUDED__
#define __ZHELPERS_H_INCLUDED__

//  Include a bunch of headers that we will need in the examples

#include "zmq.h"

#include <assert.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#if (!defined(WIN32))
#include <sys/time.h>
#endif

#if (defined(WIN32))
#include <windows.h>
#endif

//  Version checking, and patch up missing constants to match 2.1
#if ZMQ_VERSION_MAJOR == 2
#error "Please upgrade to ZeroMQ/3.2 for these examples"
#endif

//  On some version of Windows, POSIX subsystem is not installed by default.
//  So define srandom and random ourself.
#if (defined(WIN32))
#define srandom srand
#define random  rand
#endif

//  Provide random number from 0..(num-1)
#define randof(num) (int)((float)(num) * random() / (RAND_MAX + 1.0))

//  Receive 0MQ string from socket and convert into C string
//  Caller must free returned string. Returns NULL if the context
//  is being terminated.
static char *
s_recv(void *socket)
{
    enum
    {
        cap = 256
    };
    char buffer[cap];
    int size = zmq_recv(socket, buffer, cap - 1, 0);
    if (size == -1)
        return NULL;
    buffer[size < cap ? size : cap - 1] = '\0';

#if (defined(WIN32))
    return strdup(buffer);
#else
    return strndup(buffer, sizeof(buffer) - 1);
#endif

    // remember that the strdup family of functions use malloc/alloc for space for the new string.  It must be manually
    // freed when you are done with it.  Failure to do so will allow a heap attack.
}

//  Convert C string to 0MQ string and send to socket
static int
s_send(void *socket, char *string)
{
    int size = zmq_send(socket, string, strlen(string), 0);
    return size;
}

//  Sends string as 0MQ string, as multipart non-terminal
static int
s_sendmore(void *socket, char *string)
{
    int size = zmq_send(socket, string, strlen(string), ZMQ_SNDMORE);
    return size;
}

//  Receives all message parts from socket, prints neatly
//
static void
s_dump(void *socket)
{
    int rc;

    zmq_msg_t message;
    rc = zmq_msg_init(&message);
    assert(rc == 0);
    (void)rc;

    puts("----------------------------------------");
    //  Process all parts of the message
    do
    {
        int size = zmq_msg_recv(&message, socket, 0);
        assert(size >= 0);

        //  Dump the message as text or binary
        char *data = (char *)zmq_msg_data(&message);
        assert(data != 0);
        int is_text = 1;
        int char_nbr;
        for (char_nbr = 0; char_nbr < size; char_nbr++)
        {
            if ((unsigned char)data[char_nbr] < 32 || (unsigned char)data[char_nbr] > 126)
            {
                is_text = 0;
            }
        }

        printf("[%03d] ", size);
        for (char_nbr = 0; char_nbr < size; char_nbr++)
        {
            if (is_text)
            {
                printf("%c", data[char_nbr]);
            }
            else
            {
                printf("%02X", (unsigned char)data[char_nbr]);
            }
        }
        printf("\n");
    } while (zmq_msg_more(&message));

    rc = zmq_msg_close(&message);
    assert(rc == 0);
}

#if (!defined(WIN32))
//  Set simple random printable identity on socket
//  Caution:
//    DO NOT call this version of s_set_id from multiple threads on MS Windows
//    since s_set_id will call rand() on MS Windows. rand(), however, is not
//    reentrant or thread-safe. See issue #521.
static void
s_set_id(void *socket)
{
    char identity[10];
    sprintf(identity, "%04X-%04X", randof(0x10000), randof(0x10000));
    zmq_setsockopt(socket, ZMQ_IDENTITY, identity, strlen(identity));
}
#else
//  Fix #521 for MS Windows.
static void
s_set_id(void *socket, intptr_t id)
{
    char identity[10];
    sprintf(identity, "%04X", (int)id);
    zmq_setsockopt(socket, ZMQ_IDENTITY, identity, strlen(identity));
}
#endif

//  Sleep for a number of milliseconds
static void
s_sleep(int msecs)
{
#if (defined(WIN32))
    Sleep(msecs);
#else
    struct timespec t;
    t.tv_sec = msecs / 1000;
    t.tv_nsec = (msecs % 1000) * 1000000;
    nanosleep(&t, NULL);
#endif
}

//  Return current system clock as milliseconds
static int64_t
s_clock(void)
{
#if (defined(WIN32))
    SYSTEMTIME st;
    GetSystemTime(&st);
    return (int64_t)st.wSecond * 1000 + st.wMilliseconds;
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (int64_t)(tv.tv_sec * 1000 + tv.tv_usec / 1000);
#endif
}

//  Print formatted string to stdout, prefixed by date/time and
//  terminated with a newline.

static void
s_console(const char *format, ...)
{
    time_t curtime = time(NULL);
    struct tm *loctime = localtime(&curtime);
    char *formatted = (char *)malloc(20);
    strftime(formatted, 20, "%y-%m-%d %H:%M:%S ", loctime);
    printf("%s", formatted);
    free(formatted);

    va_list argptr;
    va_start(argptr, format);
    vprintf(format, argptr);
    va_end(argptr);
    printf("\n");
}

#endif //  __ZHELPERS_H_INCLUDED__
